import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  forwardRef,
  Input,
  OnInit,
  TemplateRef,
  ViewChild,
  ViewEncapsulation,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { formatDate } from '@angular/common';
import { I18NService, LvHourMode, LvTimeFormat } from '../core/api';

@Component({
  selector: 'lv-time-picker-panel',
  templateUrl: './time-picker-panel.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof TimePickerPanelComponent => TimePickerPanelComponent),
      multi: true,
    },
  ],
  host: {
    class: 'lv-time-picker-panel',
  },
})
export class TimePickerPanelComponent implements ControlValueAccessor, OnInit {
  @Input() lvOpenObserver;

  @Input() lvHourMode: LvHourMode = '24';

  @Input() lvNowAsBaseTime: boolean = true;

  @Input() lvHeader: TemplateRef<void> = null;

  @Input() lvFooter: TemplateRef<void> = null;

  @Input() lvFormat: LvTimeFormat = 'HH:mm:ss';

  @Input() lvDisabledHour: (hour: number) => boolean;

  @Input() lvDisabledMinute: (hour: number, minute: number) => boolean;

  @ViewChild('hourContainer', { static: false }) hourContainer: ElementRef<any>;

  @ViewChild('minuteContainer', { static: false }) minuteContainer: ElementRef<any>;

  @ViewChild('secondContainer', { static: false }) secondContainer: ElementRef<any>;
  @Input() lvDisabledSecond: (hour: number, minute: number, second: number) => boolean;
  hourOptions = [];

  currentHours = '';
  minuteOptions = [];
  secondOptions = [];
  currentMinutes = '';
  currentSeconds = '';
  hourSystemOptions = ['AM', 'PM'];
  selectionClass = '';

  // 内部变量
  private _value: Date;

  constructor(
    private _cdr: ChangeDetectorRef,
    public i18n: I18NService,
  ) {}

  get showMinutes(): boolean {
    return true;
  }

  get is12Hour(): boolean {
    return this.lvHourMode === '12';
  }

  get showHours(): boolean {
    return this.lvFormat !== 'mm:ss';
  }

  get showSeconds(): boolean {
    return !['hh:mm a', 'hh:mm A', 'HH:mm'].includes(this.lvFormat);
  }

  ngOnInit(): void {
    this.initTimeView();
    this.initSelectionClass();
  }

  initSelectionClass(): void {
    this.selectionClass = '';
    let count = 0;

    if (this.is12Hour) {
      count++;
    }

    if (this.showHours) {
      count++;
    }

    if (this.showMinutes) {
      count++;
    }

    if (this.showSeconds) {
      count++;
    }

    if (count < 3) {
      this.selectionClass = 'lv-time-picker-panel-selection-large';
    }
  }

  trackByIndex(index): any {
    return index;
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouch = fn;
  }

  close(): void {
    this.lvOpenObserver?.next(false);
  }

  toTimeString(value: number): string {
    return value < 10 ? `0${value}` : value.toString();
  }

  selectJustNow(): void {
    const date = new Date();
    this._value = date;
    this._onChange(this._value);
    this.initTimeView(); // 重新初始化时间数组

    this.close();
  }

  setTimer(type, item): void {
    if (item.isDisabled) {
      return;
    }

    this._value = this.getCurrentDate(type, item);
    this._onChange(this._value);
    this.setPanelHighlightPosition();
    this.initTimeView(); // 重新初始化时间数组
  }

  formatHour(value: number): string | number {
    if (['hh:mm a', 'hh:mm A', 'hh:mm:ss a', 'hh:mm:ss A'].includes(this.lvFormat)) {
      const date = new Date();
      date.setHours(Number(value));
      const str = `${formatDate(date, 'hh a', 'en-us')}`;
      return ['hh:mm a', 'hh:mm:ss a'].includes(this.lvFormat) ? str.toLocaleLowerCase() : str;
    } else {
      return value;
    }
  }

  setBaseTime(): void {
    // 在选择时间时，把当前时间作为自动补齐的基准时间
    if (this.lvNowAsBaseTime) {
      const date = new Date();
      if (this.currentHours === '') {
        this.currentHours = this.toTimeString(date.getHours());
      }
      if (this.currentMinutes === '') {
        this.currentMinutes = this.toTimeString(date.getMinutes());
      }
      if (this.currentSeconds === '') {
        this.currentSeconds = this.toTimeString(date.getSeconds());
      }
    }
  }

  writeValue(value: any): void {
    if (value) {
      const _h = value.getHours();
      const _m = value.getMinutes();
      const _s = value.getSeconds();
      this.currentHours = _h < 10 ? `0${_h}` : _h.toString();
      this.currentMinutes = _m < 10 ? `0${_m}` : _m.toString();
      this.currentSeconds = _s < 10 ? `0${_s}` : _s.toString();
      if (this._value?.getTime() !== (value && value.getTime())) {
        this._value = value;
        this.setPanelHighlightPosition();
      }
    } else {
      this.currentHours = '';
      this.currentMinutes = '';
      this.currentSeconds = '';
    }

    this.initTimeView();
    this._cdr.markForCheck();
  }

  initTimeView(): void {
    this.hourOptions = Array.from({ length: 24 }).map(
      (
        item,
        key,
      ): {
        isSelected: boolean;
        isDisabled: boolean;
        value: string;
      } => ({
        value: this.toTimeString(key),
        isDisabled: this.lvDisabledHour?.(key),
        isSelected: this.currentHours === this.toTimeString(key),
      }),
    );
    this.minuteOptions = Array.from({ length: 60 }).map(
      (
        item,
        key,
      ): {
        isSelected: boolean;
        isDisabled: boolean;
        value: string;
      } => ({
        value: this.toTimeString(key),
        isDisabled: this.lvDisabledMinute ? this.lvDisabledMinute(Number(this.currentHours), key) : undefined,
        isSelected: this.currentMinutes === this.toTimeString(key),
      }),
    );
    this.secondOptions = Array.from({ length: 60 }).map(
      (
        item,
        key,
      ): {
        isSelected: boolean;
        isDisabled: boolean;
        value: string;
      } => ({
        value: this.toTimeString(key),
        isDisabled: this.lvDisabledSecond?.(Number(this.currentHours), Number(this.currentMinutes), key),
        isSelected: this.currentSeconds === this.toTimeString(key),
      }),
    );
  }

  getOptionalTimer(type, value): any {
    let item;
    if (type === 'hour') {
      const flag = value !== '' && (!this.lvDisabledHour || (this.lvDisabledHour && !this.lvDisabledHour(value)));
      if (flag) {
        return value;
      } else {
        item = this.hourOptions.find((i, key): boolean => this.lvDisabledHour && !this.lvDisabledHour(key));
      }
    }

    if (type === 'minute') {
      const flag =
        value !== '' &&
        (!this.lvDisabledMinute || (this.lvDisabledMinute && !this.lvDisabledMinute(Number(this.currentHours), value)));
      if (flag) {
        return value;
      } else {
        item = this.minuteOptions.find(
          (i, key): boolean => this.lvDisabledMinute && !this.lvDisabledMinute(Number(this.currentHours), key),
        );
      }
    }

    if (type === 'second') {
      const flag =
        value !== '' &&
        (!this.lvDisabledSecond ||
          (this.lvDisabledSecond &&
            !this.lvDisabledSecond(Number(this.currentHours), Number(this.currentMinutes), value)));
      if (flag) {
        return value;
      } else {
        item = this.secondOptions.find(
          (i, key): boolean =>
            this.lvDisabledSecond &&
            !this.lvDisabledSecond(Number(this.currentHours), Number(this.currentMinutes), key),
        );
      }
    }

    return item !== undefined ? item.value : '00';
  }

  /**
   * 定位到选中的位置
   *
   */
  setPanelHighlightPosition(): void {
    let contentDomes = [[this.minuteContainer.nativeElement, Number(this.currentMinutes)]];
    this.lvFormat === 'HH:mm:ss' &&
      (contentDomes = [
        ...contentDomes,
        ...[
          [this.hourContainer.nativeElement, Number(this.currentHours)],
          [this.secondContainer.nativeElement, Number(this.currentSeconds)],
        ],
      ]);
    this.lvFormat === 'HH:mm' &&
      (contentDomes = [...contentDomes, ...[[this.hourContainer.nativeElement, Number(this.currentHours)]]]);
    this.lvFormat === 'mm:ss' &&
      (contentDomes = [...contentDomes, ...[[this.secondContainer.nativeElement, Number(this.currentSeconds)]]]);

    contentDomes.forEach((item): void => {
      const contentDom = item[0];
      const index = item[1];
      const li = contentDom.querySelectorAll('li')[index];
      // 缓冲滚动定位
      let duration = 120;
      const loop = (): void => {
        if (!contentDom) {
          return;
        }

        const _moveTo = li.offsetTop - contentDom.offsetTop;
        const _scrollTop = contentDom.scrollTop;
        const speed = ((_moveTo - _scrollTop) / duration) * 10;

        if (duration <= 0) {
          contentDom.scrollTop = _moveTo;
          return;
        }

        contentDom.scrollTop = _scrollTop + speed;
        if (_moveTo !== _scrollTop) {
          duration = duration - 5;
          requestAnimationFrame(loop);
        }
      };
      loop();
    });
  }

  private _onTouch: any = (): void => {};

  private _onChange: any = (): void => {};

  private getCurrentDate(type: any, item: any): Date {
    const date = new Date();
    if (type === 'hour') {
      this.currentHours = item.value;
    }
    if (type === 'minute') {
      this.currentMinutes = item.value;
    }
    if (type === 'second') {
      this.currentSeconds = item.value;
    }

    this.setBaseTime();

    this.currentHours = this.getOptionalTimer('hour', this.currentHours);
    this.currentMinutes = this.getOptionalTimer('minute', this.currentMinutes);
    this.currentSeconds = this.getOptionalTimer('second', this.currentSeconds);

    date.setHours(Number(this.currentHours));
    date.setMinutes(Number(this.currentMinutes));
    date.setSeconds(Number(this.currentSeconds));
    return date;
  }
}
